<?php

/**
 * Maikuro Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2010, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

require_once 'element/AbstractFormElement.php';
require_once 'element/CheckboxFormElement.php';
require_once 'element/FileFormElement.php';
require_once 'element/FormElementGroup.php';
require_once 'element/HiddenFormElement.php';
require_once 'element/PasswordFormElement.php';
require_once 'element/RadioFormElement.php';
require_once 'element/SelectFormElement.php';
require_once 'element/SubmitFormElement.php';
require_once 'element/TextFormElement.php';
require_once 'element/TextareaFormElement.php';
require_once 'decorator/HTMLFormDecorator.php';

class Form
{
    const METHOD_POST = 'post';
    const METHOD_GET  = 'get';

    /**
     * @var string
     */
    protected $action;

    /**
     * @var IFormDecorator
     */
    private $decorator;

    /**
     * @var array
     */
    private $elements = array();

    /**
     * @var string
     */
    protected $method = 'post';

    /**
     * @var boolean
     */
    protected $valid = true;

    /**
     *
     * @param IFormDecorator $decorator
     * @return Form
     */
    public function __construct(IFormDecorator $decorator = null)
    {
        $this->decorator = $decorator;
        $this->setup();
        return $this;
    }

    /**
     * "Magic" method to get a form element by trying to access it like a public
     * property, i.e. $this->myFormElement.
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name) {
        return $this->getElement($name);
    }

    /**
     * "Magic" method to check if a form element is set by using the 'isset'
     * function.
     *
     * @param string $name
     * @return boolean
     */
    public function __isset($name) {
        return isset($this->elements[$name]);
    }

    /**
     * "Magic" method to unset a form element using the 'unset' function.
     *
     * @param string $name
     * @return void
     */
    public function __unset($name) {
        $this->removeElement($name);
    }

    /**
     * Returns the action URL of the form.
     *
     * @return string
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * Returns the form decorator (if any).
     *
     * @return IFormDecorator
     */
    public function getDecorator()
    {
        return $this->decorator;
    }

    /**
     * Returns the element with the specified name.
     *
     * @param string $name
     * @return AbstractFormElement
     */
    public function getElement($name)
    {
        if (isset($this->elements[(string) $name])) {
            return $this->elements[$name];
        }
        return null;
    }

    /**
     * Returns the elements of the form. If an $elementNames array is
     * supplied, only the chosen elements will be returned.
     *
     * @param array $elementNames
     * @return array
     */
    public function getElements(array $elementNames = null) {
        if ($elementNames !== null) {
            $elements = array();
            foreach ($elementNames as $elementName) {
                $elements[$elementName] = $this->getElement($elementName);
            }
            return $elements;
        }
        return $this->elements;
    }

    /**
     * Gets all the form elements data as an associative array
     * with the element names as keys and their values as values.
     *
     * @return array
     */
    public function getData(array $forElements = null)
    {
        $data = array();
        foreach ($this->elements as $name => $element) {
            if ($forElements != null && !in_array($name, $forElements)) {
                continue;
            }
            if ($element instanceof AbstractFormElement) {
                $data[$name] = $element->getValue();
            } else {
                $data[$name] = $element->getValues();
            }
        }
        return $data;
    }

    /**
     * Returns the form's method.
     *
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * Returns validation messages of all elements.
     *
     * @return array
     */
    public function getValidationMessages()
    {
        $messages = array();
        foreach ($this->elements as $element) {
            if ($element->getValidationMessages() !== null) {
                $messages = array_merge($messages, $element->getValidationMessages());
            }
        }
        return $messages;
    }

    /**
     * Enter description here...
     *
     * @return boolean
     */
    public function isValid()
    {
        foreach ($this->elements as $element) {
            if (!$element->isValid()) {
                $this->valid = false;
            }
        }
        return $this->valid;
    }

    /**
     * Removes the form element with the specified name.
     *
     * @param string $elementName
     * @return Form
     */
    public function removeElement($elementName)
    {
        unset($this->elements[$elementName]);
        return $this;
    }

    /**
     * Sets the action URL.
     *
     * @param string $action
     * @return Form
     */
    public function setAction($action)
    {
        $this->action = $action;
        return $this;
    }
    
    /**
     * Sets values to the elements added to this form.
     *
     * @param array $data
     * @return Form
     */
    public function setData(array $data)
    {
        foreach ($this->elements as $element) {
            if (array_key_exists($element->getName(), $data)) {
                if ($element instanceof AbstractFormElement) {
                    $element->setValue($data[$element->getName()]);
                } else {
                    $element->setValues($data[$element->getName()]);
                }
            }
        }
        return $this;
    }

    /**
     * Sets a form decorator.
     *
     * @param IFormDecorator $decorator
     * @return Form
     */
    public function setDecorator(IFormDecorator $decorator)
    {
        $this->decorator = $decorator;
        foreach ($this->elements as $element) {
            $element->setDecorator($decorator);
        }
        return $this;
    }

    /**
     * Sets the element to the form.
     *
     * @param AbstractFormElement|FormElementGroup $element
     * @return Form
     */
    public function setElement($element)
    {
        if (!$element instanceof AbstractFormElement 
            && !$element instanceof FormElementGroup) {
                throw new InvalidArgumentException('Form elements must be instances of either AbstractFormElement or FormElementGroup.');
        }

        if ($this->decorator != null) {
            $element->setDecorator($this->decorator);
        }
        $this->elements[$element->getName()] = $element;
        return $this;
    }

    /**
     * Sets an array of elements to the form.
     *
     * @param array $elements
     * @return Form
     */
    public function setElements(array $elements)
    {
        foreach ($elements as $element) {
            $this->setElement($element);
        }
        return $this;
    }
    
    /**
     * Sets the method (POST/GET);
     *
     * @param string $method
     * @return Form
     */
    public function setMethod($method)
    {
        if ($method != self::METHOD_GET && $method != self::METHOD_POST) {
            throw new InvalidArgumentException('The form method must be either "get" or "post".');
        }
        $this->method = $method;
        return $this;
    }

    /**
     * 
     */
    protected function setup()
    {
        
    }

    public function toString()
    {
        if ($this->decorator == null) {
            var_dump($this);
        } else {
            return $this->decorator->decorateForm($this);
        }
    }
}